Skip to content

Pin coalescing under reports from many threads at once - #45

Merged
dmccoystephenson merged 1 commit into
mainfrom
feature/concurrent-report-tests
Nov 11, 2026
Merged

dmccoystephenson merged 1 commit into
mainfrom
feature/concurrent-report-tests

Conversation

@dmccoystephenson

Copy link
Copy Markdown
Member

Stage B — unit-test expansion (coalescing under concurrent report() calls).

Summary

  • report() is called from the host's threads (the lock comment in TraceClient says so: "report() runs on the host's threads, flushes run on the sender thread"), but no test called it from more than one thread. Every existing coalescing test reports from the test thread alone.
  • coalesce_reportsFromManyThreadsAtOnceAreEachCountedExactlyOnce: 8 threads report the same event 500 times each, all released by one latch. With the real one-minute window, exactly two events are sent: the first report, and the count close() sends with coalesced = 3999. This pins the documented counting formula (sum of coalesced where present, 1 where not) under contention.
  • coalesce_distinctEventsFromManyThreadsAtOnceAreEachSentOnceAndCountedOnce: 8 threads open 5 windows each at the same moment and repeat each event 3 times. Every one of the 40 events is sent exactly once as a first report and once as a count of 2. Both totals are asserted to sit within QUEUE_CAPACITY and MAX_COALESCED_EVENTS, so a drop would be a bug in the client, not the expected bound.
  • A small runAtOnce helper starts the threads behind a CountDownLatch and joins them with a 10 s bound.
  • These are characterization tests. Production code is unchanged, and both tests pass against the current main.

Test plan

  • mvn -B verify locally (Java 21): Tests run: 111, Failures: 0, Errors: 0, Skipped: 0, with both new tests present in the surefire report
  • Both new tests re-run three more times on their own; all green (about 0.3 s per run)
  • CI matrix build (8), build (17), build (21) green on the PR head

Skipped issues

Every open issue is waiting on a maintainer decision, so none was picked for this cycle:

No tracking issue. The gap was found during Stage B triage.

This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).

🤖 Generated with Claude Code


drafted by Claude on behalf of Daniel Stephenson

report() runs on the host's threads, but no test called it from more
than one. Eight threads now report one event 500 times each, and the
first report plus the count close() sends add up to all 4000. Eight
threads also open five windows each at the same moment, and every event
is sent once and counted once, with nothing dropped below the queue and
window limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dmccoystephenson

Copy link
Copy Markdown
Member Author

Self-review rubric (head feature/concurrent-report-tests, CI run 42949149041):

  • Scope: PASS — the diff is limited to TraceClientTest.java (+97 lines): one import, one helper, two tests. No production, README or pom change.
  • Tests-new: PASS (not applicable) — no public method was added.
  • Tests-fix: PASS (not applicable) — this is a Stage B characterization PR with no bug fix, and both tests pass against unchanged main code.
  • Sibling structure: PASS — no new file. The tests follow the coalesce_* naming, the // Arrange / // Act / // Assert sections and the shared server / received fixtures. runAtOnce sits beside the other private helpers in the coalescing section and has the same one-line Javadoc style as awaitReceived.
  • Sibling renames: PASS (not applicable) — nothing was renamed.
  • Docs: PASS — no documented behaviour changed. The report(String, Double, Map) Javadoc and the README counting formula are what the first test asserts.
  • Issue resolution: PASS (not applicable) — the PR has no Closes. The PR body records that the gap was found during triage and gives a skip reason for every open issue.
  • CI: PASS — build (8) 27 s, build (17) 37 s and build (21) 35 s all passed on the PR head.
  • No dependencies / Single file / Java 8: PASS — pom.xml and src/main are untouched. The test uses only java.util.function.IntConsumer, Thread and CountDownLatch (all Java 8), and the Java 8 leg is green.
  • Never throws / Non-blocking & bounded / Environment seam / Version agreement / Opt-out contract: PASS (not applicable) — production code is untouched.

Judgment calls, folded in here rather than posted as inline comments:

  • src/test/java/software/stephenson/trace/TraceClientTest.java coalesce_reportsFromManyThreadsAtOnceAreEachCountedExactlyOnce: green against correct code does not show that the test would catch a real race. No mutation run was done (for example, moving window.count++ outside synchronized (lock)), so how well these tests detect a race is unmeasured. A lost update would show up as a coalesced value below 3999, which is the assertion, but the test only reproduces the race with some probability.
  • The distinct-events test sends 80 requests and depends on close() draining them within READ_TIMEOUT_MS (5 s). This took about 0.3 s locally and passed on the Java 8 leg. If it ever flakes on a slow runner, this bound is the first thing to check. It should not be loosened silently.

Summary: a test-only, green, in-scope Stage B PR. No path on the do-not-auto-merge list is touched.

This review comment was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).


drafted by Claude on behalf of Daniel Stephenson

@dmccoystephenson
dmccoystephenson merged commit 346c2c0 into main Nov 11, 2026
3 checks passed
@dmccoystephenson
dmccoystephenson deleted the feature/concurrent-report-tests branch November 11, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant